package com.vst.orm.search.hibernate;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.proxy.HibernateProxyHelper;

import com.vst.orm.search.Metadata;
import com.vst.orm.util.MetadataUtil;

public class HibernateMetadataUtil implements MetadataUtil {
	private static Map<SessionFactory, HibernateMetadataUtil> map = new HashMap();
	private SessionFactory sessionFactory;

	public static HibernateMetadataUtil getInstanceForSessionFactory(SessionFactory sessionFactory) {
		HibernateMetadataUtil instance = (HibernateMetadataUtil) map.get(sessionFactory);
		if (instance == null) {
			instance = new HibernateMetadataUtil();
			instance.sessionFactory = sessionFactory;
			map.put(sessionFactory, instance);
		}
		return instance;
	}

	public Serializable getId(Object entity) {
		if (entity == null) {
			throw new NullPointerException("Cannot get ID from null object.");
		}
		return get(entity.getClass()).getIdValue(entity);
	}

	public boolean isId(Class<?> rootClass, String propertyPath) {
		if ((propertyPath == null) || ("".equals(propertyPath))) {
			return false;
		}
		if ((propertyPath.equals("id")) || ((propertyPath.endsWith(".id"))
				&& (get(rootClass, propertyPath.substring(0, propertyPath.length() - 3)).isEntity()))) {
			return true;
		}
		int pos = propertyPath.lastIndexOf(".");
		if (pos != -1) {
			Metadata parentType = get(rootClass, propertyPath.substring(0, pos));
			if (!parentType.isEntity()) {
				return false;
			}
			return propertyPath.substring(pos + 1).equals(parentType.getIdProperty());
		}
		return propertyPath.equals(this.sessionFactory.getClassMetadata(rootClass).getIdentifierPropertyName());
	}

	public Metadata get(Class<?> entityClass) {
		entityClass = getUnproxiedClass(entityClass);
		ClassMetadata cm = this.sessionFactory.getClassMetadata(entityClass);
		if (cm == null) {
			throw new IllegalArgumentException("Unable to introspect " + entityClass.toString()
					+ ". The class is not a registered Hibernate entity.");
		}
		return (Metadata) new HibernateEntityMetadata(this.sessionFactory, cm, null);
	}

	public Metadata get(Class<?> rootEntityClass, String propertyPath) {
		try {
			Metadata md = get(rootEntityClass);
			if ((propertyPath == null) || ("".equals(propertyPath))) {
				return md;
			}
			String[] chain = propertyPath.split("\\.");
			for (int i = 0; i < chain.length; i++) {
				md = md.getPropertyType(chain[i]);
			}
			return md;
		} catch (HibernateException ex) {
			throw new PropertyNotFoundException(
					"Could not find property '" + propertyPath + "' on class " + rootEntityClass + ".");
		}
	}

	public <T> Class<T> getUnproxiedClass(Class<?> klass) {
		while (this.sessionFactory.getClassMetadata(klass) == null) {
			klass = klass.getSuperclass();
			if (Object.class.equals(klass)) {
				return null;
			}
		}
		return (Class<T>) klass;
	}

	public <T> Class<T> getUnproxiedClass(Object entity) {
		return HibernateProxyHelper.getClassWithoutInitializingProxy(entity);
	}
}
